import buffer from '@ohos.buffer';
import { nfcConfig } from '../config';
import * as system from './system';
import { BLUETOOTH_STATE, BLE_SCAN_DUTY, BLE_MATCH_MODE } from './constant';
import { log, waitTime } from '../util';

import type { ScanResult, DiscoverDevice, Buffer, WifiInfo, BluetoothState } from './type'

const TAG = 'discover_service'

class Discover {
  private discoverHandlerMap: Map<string | symbol, (device: DiscoverDevice) => void> = new Map()
  private lastDiscoverTime: number = 0;
  private minDiscoverInterval: number = 5 * 1000;
  private maxWaitNum: number = 10;

  constructor() {
    this.init()
  }

  private init() {
    log.info(TAG, 'init')
    system.onBLEDeviceFind((scanResult) => {
      log.info(TAG, 'onBLEDeviceFind', scanResult.length)
      const currentTime = Date.now()
      if (this.lastDiscoverTime === 0 || (this.lastDiscoverTime && currentTime - this.lastDiscoverTime > this.minDiscoverInterval)) {
        const result = this.filterScanResult(scanResult)
        if (result) {
          const advData = this.getAdvData(result.data)
          if (advData) {
            const wifiInfo = this.resolveWifiInfo(advData)
            if (wifiInfo) {
              this.lastDiscoverTime = currentTime
              this.discoverHandlerMap.forEach((handle) => {
                handle({
                  id: result.deviceId,
                  name: result.deviceName,
                  rssi: result.rssi,
                  wifiInfo,
                })
              })
            }
          }
        }
      }
    })
  }

  private filterScanResult(scanResult: ScanResult[]): ScanResult {
    const list = scanResult.filter(item => item.deviceName.startsWith(nfcConfig.broadcastPrefix) && item.rssi >= nfcConfig.discoverRssi)
    if (list.length === 0) {
      return null
    }
    else if (list.length > 1) {
      list.sort((a, b) => a.rssi - b.rssi)
    }
    return list[0]
  }

  private getAdvData(data: ArrayBuffer): Buffer {
    const bf = buffer.from(data)
    let index = 0;
    let len = bf.readUInt8(index);
    let type = bf.readUInt8(index + 1);
    while (type !== 0x16 && index < bf.length) {
      index = index + len + 1
      if (index < bf.length) {
        len = bf.readUInt8(index)
        type = bf.readUInt8(index + 1)
      }
    }
    if (type === 0x16) {
      const nfcData = buffer.alloc(len - 3)
      bf.copy(nfcData, 0, index + 4, index + len + 1)
      return nfcData
    } else {
      return null
    }
  }

  private resolveWifiInfo(bf: Buffer): WifiInfo {
    const info: WifiInfo = {
      address: '',
      ssid: '',
      password: ''
    }
    let index = 0
    info.address = info.address + bf.readUInt8(index).toString() + '.'
    index = index + 1
    info.address = info.address + bf.readUInt8(index).toString() + '.'
    index = index + 1
    info.address = info.address + bf.readUInt8(index).toString() + '.'
    index = index + 1
    info.address = info.address + bf.readUInt8(index).toString()
    index = index + 2
    const ssidEndIndex = bf.indexOf(0xFF, index)
    info.ssid = bf.toString('utf8', index, ssidEndIndex)
    index = ssidEndIndex + 1
    info.password = bf.toString('utf8', index)
    return info
  }

  private async waitBluetoothState(state: BluetoothState): Promise<boolean> {
    let currentState = system.getState()
    let num = 0
    while (num < this.maxWaitNum && currentState !== state) {
      await waitTime()
      currentState = system.getState()
      num = num + 1
    }
    return currentState === state
  }

  async enableBluetooth(): Promise<boolean> {
    let result = false
    let operateResult = false
    const state = system.getState()
    if (state === BLUETOOTH_STATE.STATE_OFF) {
      operateResult = system.enableBluetooth()
      if (operateResult) {
        result = await this.waitBluetoothState(BLUETOOTH_STATE.STATE_ON)
      }
    }
    else if (state === BLUETOOTH_STATE.STATE_TURNING_ON) {
      result = await this.waitBluetoothState(BLUETOOTH_STATE.STATE_ON)
    }
    else if (state === BLUETOOTH_STATE.STATE_ON) {
      result = true
    }
    else if (state === BLUETOOTH_STATE.STATE_TURNING_OFF) {
      operateResult = await this.waitBluetoothState(BLUETOOTH_STATE.STATE_OFF)
      if (operateResult) {
        operateResult = system.enableBluetooth()
      }
      if (operateResult) {
        result = await this.waitBluetoothState(BLUETOOTH_STATE.STATE_ON)
      }
    }
    return result
  }

  async startFound(): Promise<boolean> {
    let result = await this.enableBluetooth()
    if (result) {
      result = system.startBLEScan(null, {
        interval: 0,
        dutyMode: BLE_SCAN_DUTY.SCAN_MODE_LOW_LATENCY,
        matchMode: BLE_MATCH_MODE.MATCH_MODE_AGGRESSIVE
      })
    }
    log.info(TAG, 'startFound', result)
    return result
  }

  stopFound(): boolean {
    const result = system.stopBLEScan()
    if (result) {
      this.lastDiscoverTime = 0
    }
    log.info(TAG, 'stopFound', result)
    return result
  }

  onDeviceDiscover(handler: (device: DiscoverDevice) => void, key: string | symbol = Symbol()) {
    this.discoverHandlerMap.set(key, handler)
  }

  offDeviceDiscover(key?: string) {
    if (key === undefined) {
      this.discoverHandlerMap.clear()
    } else {
      if (this.discoverHandlerMap.has(key)) {
        this.discoverHandlerMap.delete(key)
      }
    }
  }
}

const nfcDiscover = new Discover()

export default nfcDiscover

export type { DiscoverDevice, WifiInfo }